import fs = require('fs');
import ejs = require('ejs');
import path = require('path');
import http = require('http');
import morgan = require('morgan');
import express = require('express');
import favicon = require('serve-favicon');
import bodyParser = require('body-parser');
import session = require('express-session');
import cookieParser = require('cookie-parser');
import connectMongo = require('connect-mongo');
import { Request, Response, NextFunction, RequestHandler, Router } from "express";

import Mongo from "./mongo";
import Logger from "./logger";
import { Magics } from "./magics";
import { IUnknown } from "./types";
import * as utils from '../core/utils';
import { MongoColls, MongoCollNames } from "../config/mongo";

export declare class Error {
    public name: string;
    public message: string;
    public stack: string;
    public code: any;
    public errno: number;
    public syscall: string;

    constructor(message: string);
}

declare function require(module: string): any;
const MongoStore = connectMongo(session);

export default class PWSApp implements IUnknown {
    private _port: number;
    private _name: string;
    private _logger: Logger;
    private _bindAddr: string;
    private _server: http.Server;
    private _app: express.Express;
    private _mongo: Mongo       = null;
    private _isRunning: boolean = false;
    private _statics: string    = path.join(__dirname, '../statics');

    constructor(name: string, port: number, bind: string = '127.0.0.1') {
        this._port     = port;
        this._name     = name;
        this._bindAddr = bind;
        this._mongo    = new Mongo();
        this._logger   = new Logger(this._name);
    }

    get db(): Mongo {
        return this._mongo;
    }

    get name(): string {
        return this._name;
    }

    get logger(): Logger {
        return this._logger;
    }

    toString() {
        return this.name;
    }

    isDebug() {
        //noinspection TypeScriptUnresolvedVariable
        return this._app.get('env') === 'development' || process.env.PPWS_DEBUG === 'true';
    }

    registerRoutes(path: any, router: Router|RequestHandler) {
        this._app.use(path, router);
    }

    run() {
        if( this._isRunning )
            return;
        this._app = express();
        this._mongo.connect(this.isDebug()).then(() => {
            this._setupExpress();
            this._loadActions();
            //this._setupErrHandler();
            this._setupHttpServer();
            this._server.listen(this._port, this._bindAddr);
        }).catch((err: Error) => {
            this.logger.E(this.name, err);
            this._isRunning = false;
        });
    }

    private _loadActions() {
        if( this.name === Magics.AllInOne ) {
            require(`../actions/api/loader`)(this);
            require(`../actions/admin/loader`)(this);
        } else {
            require(`../actions/${this._name.toLocaleLowerCase()}/loader`)(this);
        }
    }

    private _setupExpress() {
        let app   = this._app;

        app.use(favicon(path.join(this._statics, 'favicon.png')));
        // view engine setup
        app.engine('.html', ejs['__express']);
        app.set('view engine', 'html');

        //noinspection TypeScriptValidateTypes
        app.use(morgan(this.isDebug() ? 'dev' : 'common'));
        app.use(function(req, res, next) {
            res.setHeader('Access-Control-Allow-Origin', '*');
            res.setHeader('Access-Control-Allow-Methods', 'POST');
            res.setHeader('Access-Control-Allow-Headers', 'x-requested-with,content-type');
            next();
        });
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({
                                          extended      : true,
                                          limit         : Magics.Byte2M,
                                          parameterLimit: Magics.BodyParamLimit
                                      }));
        app.use(cookieParser(Magics.CookieSecret));
        app.use(express.static(this._statics));

        // session storage
        let storeOption = {
            url       : this._mongo.connectUrl(this.isDebug()),
            touchAfter: Magics.TimeSec4H,
            collection: MongoCollNames[MongoColls.OTHER_SESSION]
        };
        let sessOptions = {
            secret           : Magics.CookieSecret,
            resave           : false,
            saveUninitialized: false,
            store            : new MongoStore(storeOption),
            cookie           : { maxAge: Magics.TimeMS4H }
        };
        app.use(session(sessOptions));
    }

    private _setupHttpServer() {
        let server: http.Server = http.createServer(this._app);
        this._server            = server;

        server.on('error', (err: Error)=> {
            if( err.syscall !== 'listen' ) {
                throw err;
            }

            switch( err.code ) {
                case 'EACCES':
                    this.logger.E(`Port ${this._port} requires elevated privileges`);
                    process.exit(1);
                    break;
                case 'EADDRINUSE':
                    this.logger.E(`Port ${this._port} is already in use`);
                    process.exit(1);
                    break;
                default:
                    throw err;
            }
        });

        server.on('listening', ()=> {
            this.logger.D(`Listening on port ${this._port}`);
        });
    }

    private _setupErrHandler() {

        // 404
        this._app.use((req: Request, res: Response, next: NextFunction) => {
            let err  = new Error('Not Found');
            err.code = 404;
            next(err);
        });

        // 5xx internal error -- develop mode
        if( this.isDebug() ) {
            this._app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
                let result = {
                    msg: err.code === 404 ? '页面未找到' : '服务器内部错误',
                    err: { code: err.code, msg: err.message, stack: err.stack }
                };
                res.status(err.code || 500);
                res.send(result);
            });
            return
        }

        // 5xx internal error -- product mode
        this._app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
            res.status(err.code || 500);
            let result = {
                msg: err.code === 404 ? '页面未找到' : '服务器内部错误',
                err: { code: err.code, msg: err.message }
            };
            res.send(result);
        });
    }
};